﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace ImgClip
{
    /// <summary>
    /// 
    /// </summary>
    public class Resolve
    {
        private static readonly string[] RESIZE_TYPE = new string[] { "fit", "fill", "fix", "pad" };
        private static readonly string[] GRAVITY = new string[] { "no", "so", "ea", "we", "ne", "nw", "se", "sw", "ce" };
        private static readonly string[] FORMAT = new string[] { "bmp", "jpg", "png", "webp", "gif" };
        private Resolve()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        public bool IsResize { get; private set; }

        /// <summary>
        /// Defines how imgclip will resize the source image
        /// fit:resizes the image while keeping aspect ratio to fit given size;
        /// fill:resizes the image while keeping aspect ratio to fill given size and cropping projecting parts;
        /// fix:Fixed width and height, force scaling.
        /// pad:Scale the original image to the largest image in the rectangle specified w and h, then center the blank with the specified color.  
        /// </summary>
        public string ResizeType { get; private set; } = "";

        /// <summary>
        /// 
        /// </summary>
        public int Width { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public int Height { get; private set; }

        /// <summary>
        /// When set, img will fill the resulting image background with the specified color. 
        /// hex_color is a hex-coded value of the color. 
        /// </summary>
        public string Background { get; private set; } = "";

        /// <summary>
        /// When set to true, it enlarges the image if it is less than the given size.  
        /// </summary>
        public bool IsEnlarge { get; private set; }

        /// <summary>
        /// Defines an area of the image to be processed (crop before resize).
        /// </summary>
        public bool IsCrop { get; private set; }

        /// <summary>
        /// no: north (top edge);
        /// so: south (bottom edge);
        /// ea: east (right edge);
        /// we: west (left edge);
        /// ne: north-east (top-right corner);
        /// nw: north-west (top-left corner);
        /// se: south-east (bottom-right corner);
        /// sw: south-west (bottom-left corner);
        /// ce: center.
        /// </summary>
        public string CropGravity { get; private set; } = "";

        /// <summary>
        /// 
        /// </summary>
        public int CropOffsetX { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public int CropOffsetY { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public int CropWidth { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public int CropHeight { get; private set; }


        /// <summary>
        /// Rotates the image on the specified angle. 
        /// </summary>
        public bool IsRotate { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public int RotateAngle { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public bool IsWatermark { get; private set; }

        /// <summary>
        /// floating point number that defines watermark size relative to the resulting image size.
        /// When set to 0 or omitted, watermark size won’t be changed.
        /// </summary>
        public double WmScale { get; private set; }

        /// <summary>
        /// watermark opacity modifier.
        /// </summary>
        public double WmOpacity { get; private set; } = 100;

        /// <summary>
        /// When set, imgproxy will use the image from the specified URL as a watermark. url is Base64-encoded URL of the custom watermark.
        /// </summary>
        public string WmUrl { get; private set; } = "";

        /// <summary>
        /// specifies the position of the watermark. 
        /// no: north (top edge);
        /// so: south (bottom edge);
        /// ea: east (right edge);
        /// we: west (left edge);
        /// ne: north-east (top-right corner);
        /// nw: north-west (top-left corner);
        /// se: south-east (bottom-right corner);
        /// sw: south-west (bottom-left corner);
        /// ce: center.
        /// </summary>
        public string WmPosition { get; private set; } = "";

        public int WmOffsetX { get; private set; }


        public int WmOffsetY { get; private set; }

        /// <summary>
        /// Redefines quality of the resulting image, percentage.
        /// </summary>
        public int Quality { get; private set; }

        /// <summary>
        /// Specifies the resulting image format. Alias for extension URL part.
        /// </summary>
        public string Format { get; private set; } = "";

        /// <summary>
        /// 
        /// </summary>
        [JsonIgnore]
        public bool IsError { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        [JsonIgnore]
        public List<string> ErrorMessage = new();

        [JsonIgnore]
        public ClipConfig ClipConfig { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="options"></param>
        /// <returns></returns>
        public static Resolve Create(string options, ClipConfig config)
        {
            if (options == null)
                return null;

            var resolve = new Resolve() { ClipConfig = config };
            options = options.Trim('/').ToLower();

            var array = options.Split("/");
            foreach (var item in array)
            {
                resolve.Resize(item).Crop(item).Rotate(item).Watermark(item).SetQuality(item).SetFormat(item);
            }
            return resolve;
        }

        /// <summary>
        /// Process image resize to put parameters  
        /// </summary>
        /// <param name="resolve"></param>
        /// <param name="value"></param>
        private Resolve Resize(string value)
        {
            if (value.StartsWith("rs:"))
            {
                this.IsResize = true;
                var _params = value.Replace("rs:", "").Trim(':').Split(":");
                foreach (var p in _params)
                {
                    if (RESIZE_TYPE.Contains(p))
                        this.ResizeType = p;
                    if (p.StartsWith("el"))
                    {
                        if (int.TryParse(p.Replace("el", ""), out int _el))
                            this.IsEnlarge = _el == 1;
                    }
                    if (p.StartsWith("bg"))
                    {
                        this.Background = $"#{p.Replace("bg", "")}";
                    }
                    if (p.StartsWith("w"))
                    {
                        if (int.TryParse(p.Replace("w", ""), out int _w))
                            this.Width = Math.Abs(_w);
                    }
                    if (p.StartsWith("h"))
                    {
                        if (int.TryParse(p.Replace("h", ""), out int _h))
                            this.Height = Math.Abs(_h);
                    }
                }
            }
            return this;
        }

        /// <summary>
        /// Process image crop to put parameters  
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private Resolve Crop(string value)
        {
            if (value.StartsWith("cr:"))
            {
                this.IsCrop = true;
                var _params = value.Replace("cr:", "").Trim(':').Split(":");
                foreach (var p in _params)
                {
                    if (p.StartsWith("gr"))
                    {
                        var gr = p.Replace("gr", "");
                        if (GRAVITY.Contains(gr))
                            this.CropGravity = gr;
                        else
                            Error($"The value: {gr} of parameter: gravity is invalid.");
                    }
                    if (p.StartsWith("w"))
                        if (int.TryParse(p.Replace("w", ""), out int _w))
                            this.CropWidth = Math.Abs(_w);
                    if (p.StartsWith("h"))
                        if (int.TryParse(p.Replace("h", ""), out int _h))
                            this.CropHeight = Math.Abs(_h);
                    if (p.StartsWith("x"))
                        if (int.TryParse(p.Replace("x", ""), out int _x))
                            this.CropOffsetX = Math.Abs(_x);
                    if (p.StartsWith("y"))
                        if (int.TryParse(p.Replace("y", ""), out int _y))
                            this.CropOffsetY = Math.Abs(_y);
                }
            }
            return this;
        }

        /// <summary>
        /// Rotates the image on the specified angle. 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private Resolve Rotate(string value)
        {
            if (value.StartsWith("rot:"))
            {
                this.IsRotate = true;
                var rotate = value.Replace("rot:", "");
                if (int.TryParse(rotate, out int _angle))
                    this.RotateAngle = _angle;
                else
                    Error($"The value: {rotate} of parameter: rotate is invalid.");
            }
            return this;
        }

        /// <summary>
        /// 设置图片质量
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private Resolve SetQuality(string value)
        {
            this.Quality = ClipConfig.Quality;
            if (value.StartsWith("qu:"))
            {
                var quality = value.Replace("qua:", "");
                if (int.TryParse(quality, out int _qua) && 0 < _qua && _qua <= 100)
                    this.Quality = _qua;
                else
                    Error($"The value: {quality} of parameter: quality is invalid.");
            }
            return this;
        }

        private Resolve SetFormat(string value)
        {
            if (value.StartsWith("ext:"))
            {
                var format = value.Replace("ext:", "");
                if (FORMAT.Contains(format))
                    this.Format = format;
                else
                    Error($"The value: {format} of parameter: format is invalid.");
            }
            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private Resolve Watermark(string value)
        {
            if (value.StartsWith("wm:"))
            {
                this.IsWatermark = true;
                var _params = value.Replace("wm:", "").Trim(':').Split(":");
                foreach (var p in _params)
                {
                    if (p.StartsWith("gr"))
                    {
                        var gr = p.Replace("gr", "");
                        if (GRAVITY.Contains(gr))
                            this.WmPosition = gr;
                        else
                            Error($"The watermark value: {gr} of parameter: gravity is invalid.");
                    }
                    if (p.StartsWith("url"))
                    {
                        this.WmUrl = Regex.Replace(p, "^url", "");
                    }
                    if (p.StartsWith("op"))
                    {
                        var op = p.Replace("op", "");
                        if (int.TryParse(op, out int _op) && _op >= 0 && _op <= 100)
                            this.WmOpacity = _op;
                        else
                            Error($"The watermark value: {op} of parameter: opacity is invalid.");
                    }
                    if (p.StartsWith("sc"))
                    {
                        var scale = p.Replace("sc", "");
                        if (int.TryParse(scale, out int _sc) && 0 <= _sc && _sc <= 100)
                            this.WmScale = _sc;
                        else
                            Error($"The watermark value: {scale} of parameter: scale is invalid.");
                    }
                    if (p.StartsWith("x"))
                        if (int.TryParse(p.Replace("x", ""), out int _x))
                            this.WmOffsetX = Math.Abs(_x);
                    if (p.StartsWith("y"))
                        if (int.TryParse(p.Replace("y", ""), out int _y))
                            this.WmOffsetY = Math.Abs(_y);
                }
                if (String.IsNullOrEmpty(WmUrl))
                    this.WmUrl = ClipConfig.WatermarkUrl;
                if (this.WmOpacity <= 0)
                    this.WmOpacity = ClipConfig.WatermarkOpacity;
            }

            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        private void Error(string message)
        {
            this.IsError = true;
            this.ErrorMessage.Add(message);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new();

            if (this.IsCrop)
            {
                sb.Append("_cr");
                sb.Append(this.CropGravity);
                if (this.CropWidth > 0)
                    sb.Append("w" + this.CropWidth);
                sb.Append("h" + this.CropHeight);
                if (this.CropOffsetX > 0)
                    sb.Append("x" + this.CropOffsetX);
                if (this.CropOffsetY > 0)
                    sb.Append("y" + this.CropOffsetY);
            }
            if (this.IsResize)
            {
                sb.Append("_rs");
                sb.Append(this.ResizeType);
                sb.Append("w" + this.Width);
                if (this.Height > 0)
                    sb.Append("h" + this.Height);
                if (this.IsEnlarge)
                    sb.Append("el" + this.IsEnlarge);
                if (!String.IsNullOrEmpty(this.Background))
                    sb.Append("bg" + this.Background);
            }
            if (this.IsRotate)
            {
                sb.Append("_rot");
                sb.Append(RotateAngle);
            }
            if (this.IsWatermark)
            {
                sb.Append("_wm");
                sb.Append(this.WmUrl);
                sb.Append(this.WmPosition);
                sb.Append(this.WmOpacity);
                sb.Append(this.WmScale);
                sb.Append(this.WmOffsetX);
                sb.Append(this.WmOffsetY);
            }
            if (this.Quality > 0)
                sb.Append($"_{this.Quality}");
            if (!String.IsNullOrEmpty(this.Format))
                sb.Append("_" + this.Format);
            return sb.ToString();
        }

    }
}
